package com.jphotomanager.io;

import java.io.UnsupportedEncodingException;

/**
 * This class is an adaptation of BufferReader from metadata-extractor project
 * at http://code.google.com/p/metadata-extractor/ by Drew Noakes
 */
public class ImageReader {

    private final byte[] _buffer;
    private boolean _isMotorolaByteOrder = true;

    public ImageReader(byte[] _buffer) {
        super();
        this._buffer = _buffer;
    }

    public int getLength() {
        return _buffer.length;
    }

    public void setMotorolaByteOrder(boolean motorolaByteOrder) {
        _isMotorolaByteOrder = motorolaByteOrder;
    }

    public boolean isMotorolaByteOrder() {
        return _isMotorolaByteOrder;
    }

    private void CheckBounds(final int index, final int bytesRequested)
            throws ArrayIndexOutOfBoundsException {
        if (bytesRequested < 0
                || index < 0
                || (long) index + (long) bytesRequested - 1L >= (long) _buffer.length)
            throw new ArrayIndexOutOfBoundsException(index);
    }

    public byte getByte(int index) throws ArrayIndexOutOfBoundsException {
        CheckBounds(index, 1);
        return _buffer[index];
    }

    public int getUInt8(int index) throws ArrayIndexOutOfBoundsException {
        CheckBounds(index, 1);

        return _buffer[index] & 255;
    }

    public int getUInt16(int index) throws ArrayIndexOutOfBoundsException {
        CheckBounds(index, 2);

        if (_isMotorolaByteOrder) {
            // Motorola - MSB first
            return (_buffer[index] << 8 & 0xFF00) | (_buffer[index + 1] & 0xFF);
        } else {
            // Intel ordering - LSB first
            return (_buffer[index + 1] << 8 & 0xFF00) | (_buffer[index] & 0xFF);
        }
    }

    public int getInt32(int index) throws ArrayIndexOutOfBoundsException {
        CheckBounds(index, 4);

        if (_isMotorolaByteOrder) {
            // Motorola - MSB first (big endian)
            return (_buffer[index] << 24 & 0xFF000000)
                    | (_buffer[index + 1] << 16 & 0xFF0000)
                    | (_buffer[index + 2] << 8 & 0xFF00)
                    | (_buffer[index + 3] & 0xFF);
        } else {
            // Intel ordering - LSB first (little endian)
            return (_buffer[index + 3] << 24 & 0xFF000000)
                    | (_buffer[index + 2] << 16 & 0xFF0000)
                    | (_buffer[index + 1] << 8 & 0xFF00)
                    | (_buffer[index] & 0xFF);
        }
    }

    public long getInt64(int index) throws ArrayIndexOutOfBoundsException {
        CheckBounds(index, 8);

        if (_isMotorolaByteOrder) {
            // Motorola - MSB first
            return ((long) _buffer[index] << 56 & 0xFF00000000000000L)
                    | ((long) _buffer[index + 1] << 48 & 0xFF000000000000L)
                    | ((long) _buffer[index + 2] << 40 & 0xFF0000000000L)
                    | ((long) _buffer[index + 3] << 32 & 0xFF00000000L)
                    | ((long) _buffer[index + 4] << 24 & 0xFF000000L)
                    | ((long) _buffer[index + 5] << 16 & 0xFF0000L)
                    | ((long) _buffer[index + 6] << 8 & 0xFF00L)
                    | ((long) _buffer[index + 7] & 0xFFL);
        } else {
            // Intel ordering - LSB first
            return ((long) _buffer[index + 7] << 56 & 0xFF00000000000000L)
                    | ((long) _buffer[index + 6] << 48 & 0xFF000000000000L)
                    | ((long) _buffer[index + 5] << 40 & 0xFF0000000000L)
                    | ((long) _buffer[index + 4] << 32 & 0xFF00000000L)
                    | ((long) _buffer[index + 3] << 24 & 0xFF000000L)
                    | ((long) _buffer[index + 2] << 16 & 0xFF0000L)
                    | ((long) _buffer[index + 1] << 8 & 0xFF00L)
                    | ((long) _buffer[index] & 0xFFL);
        }
    }

    public byte[] getBytes(int index, int count)
            throws ArrayIndexOutOfBoundsException {
        CheckBounds(index, count);
        byte[] bytes = new byte[count];
        System.arraycopy(_buffer, index, bytes, 0, count);
        return bytes;
    }

    public String getString(int index, int bytesRequested)
            throws ArrayIndexOutOfBoundsException {
        CheckBounds(index, bytesRequested);

        byte[] bytes = getBytes(index, bytesRequested);
        return new String(bytes);
    }

    public String getString(int index, int bytesRequested, String charset)
            throws ArrayIndexOutOfBoundsException {
        CheckBounds(index, bytesRequested);

        byte[] bytes = getBytes(index, bytesRequested);
        try {
            return new String(bytes, charset);
        } catch (UnsupportedEncodingException e) {
            return new String(bytes);
        }
    }
}
